package com.example.config;

import com.example.common.api.CommonResult;
import com.example.component.DynamicSecurityFilter;
import com.example.component.JwtAuthenticationTokenFilter;
import com.example.service.AuthorizeService;
import com.example.service.DynamicSecurityService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableConfigurationProperties(value = IgnoreUrlsConfig.class)
public class SecurityConfig {

    private final AuthorizeService authorizeService;

    private final IgnoreUrlsConfig ignoreUrlsConfig;

    private final BCryptPasswordEncoder passwordEncoder;

    private final ObjectMapper objectMapper;

    private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    private final DynamicSecurityService dynamicSecurityService;

    private final DynamicSecurityFilter dynamicSecurityFilter;

    @Value("${jwt.enable}")
    private boolean jwtEnable;

    @Bean
    @Primary
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        if (jwtEnable) {
            return buildJwtFilterChain(httpSecurity);
        } else {
            return buildSimpleFilterChain(httpSecurity);
        }
    }

    private SecurityFilterChain buildSimpleFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .authorizeHttpRequests()
                // 允许跨域请求的OPTIONS请求
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                // 不需要保护的资源路径允许访问
                .antMatchers(ignoreUrlsConfig.getUrls().toArray(new String[0])).permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/auth/login")
                .successHandler(this::onLoginSuccess)
                .failureHandler(this::onAuthenticationFailure)
                .and()
                .logout()
                .logoutUrl("/auth/logout")
                .logoutSuccessHandler(this::onLogoutSuccess)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(this::onAuthenticationFailure)
                .and()
                .csrf().disable()
                .build();
    }

    private SecurityFilterChain buildJwtFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests()
                // 不需要保护的资源路径允许访问
                .antMatchers(ignoreUrlsConfig.getUrls().toArray(new String[0]))
                .permitAll()
                // 允许跨域请求的OPTIONS请求
                .antMatchers(HttpMethod.OPTIONS)
                .permitAll();

        httpSecurity.csrf()// 由于使用的是JWT，我们这里不需要csrf
                .disable()
                .sessionManagement()// 基于token，所以不需要session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .anyRequest()// 除上面外的所有请求全部需要鉴权认证
                .authenticated();

        // 禁用缓存
        httpSecurity.headers().cacheControl();

        // 添加JWT filter
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        //添加自定义未授权和未登录结果返回
        httpSecurity.exceptionHandling()
                .accessDeniedHandler(this::onAccessDeniedFailure)
                .authenticationEntryPoint(this::onAuthenticationFailure);

        //有动态权限配置时添加动态权限校验过滤器
        if (dynamicSecurityService != null) {
            httpSecurity.addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);
        }
        return httpSecurity.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity security) throws Exception {
        return security.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(authorizeService)
                .passwordEncoder(passwordEncoder)
                .and()
                .build();
    }

    public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.getWriter().write(objectMapper.writeValueAsString(CommonResult.success("登录成功")));
    }

    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.getWriter().write(objectMapper.writeValueAsString(CommonResult.success("注销成功")));
    }

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.getWriter().write(objectMapper.writeValueAsString(CommonResult.unauthorized(exception.getMessage())));
    }

    public void onAccessDeniedFailure(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException {
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.getWriter().write(objectMapper.writeValueAsString(CommonResult.forbidden(exception.getMessage())));
    }
}
